﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
using Aliyeye.Car.Net.Utility;

namespace Aliyeye.Car.Net.UI.Controls
{
    public partial class Overlay : Control
    {
        #region PInvokes

        [DllImport("gdi32.dll")]
        static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth,
           int nHeight, IntPtr hObjSource, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);

        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        static extern IntPtr CreateCompatibleDC(IntPtr hdc);

        [DllImport("gdi32.dll")]
        static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight);

        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        static extern bool DeleteDC(IntPtr hdc);

        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        static extern bool DeleteObject(IntPtr hObject);

        [DllImport("user32.dll")]
        static extern IntPtr GetWindowDC(IntPtr hWnd);

        [DllImport("user32.dll")]
        static extern IntPtr GetDC(IntPtr hWnd);

        [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
        static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);

        public enum TernaryRasterOperations : uint
        {
            SRCCOPY = 0x00CC0020,
            SRCPAINT = 0x00EE0086,
            SRCAND = 0x008800C6,
            SRCINVERT = 0x00660046,
            SRCERASE = 0x00440328,
            NOTSRCCOPY = 0x00330008,
            NOTSRCERASE = 0x001100A6,
            MERGECOPY = 0x00C000CA,
            MERGEPAINT = 0x00BB0226,
            PATCOPY = 0x00F00021,
            PATPAINT = 0x00FB0A09,
            PATINVERT = 0x005A0049,
            DSTINVERT = 0x00550009,
            BLACKNESS = 0x00000042,
            WHITENESS = 0x00FF0062
        }

        Bitmap CaptureWindow(IntPtr hWndSrc, bool Client, int LeftSrc, int TopSrc, int WidthSrc, int HeightSrc)
        {
            IntPtr hDCMemory;
            IntPtr hBmp;
            IntPtr hBmpPrev;
            bool r;
            IntPtr hDCSrc;
            Bitmap ret = null;
            //int hPal;
            //int hPalPrev;
            //int RasterCapsScrn;
            //int HasPaletteScrn;
            //int PaletteSizeScrn;
            //LOGPALETTE LogPal;
            //  Depending on the value of Client get the proper device context.
            if (Client)
            {
                hDCSrc = GetDC(hWndSrc);
                //  Get device context for client area.
            }
            else
            {
                hDCSrc = GetWindowDC(hWndSrc);
                //  Get device context for entire
                //  window.
            }
            //  Create a memory device context for the copy process.
            hDCMemory = CreateCompatibleDC(hDCSrc);
            //  Create a bitmap and place it in the memory DC.
            hBmp = CreateCompatibleBitmap(hDCSrc, WidthSrc, HeightSrc);
            hBmpPrev = SelectObject(hDCMemory, hBmp);

            //  Copy the on-screen image into the memory DC.
            r = BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, LeftSrc, TopSrc, TernaryRasterOperations.SRCCOPY);
            //  Remove the new copy of the on-screen image.
            hBmp = SelectObject(hDCMemory, hBmpPrev);

            //  Release the device context resources back to the system.
            
            

            

            //  Call CreateBitmapPicture to create a picture object from the
            //  bitmap and palette handles. Then return the resulting picture
            //  object.
            ret = Image.FromHbitmap(hBmp);
            DeleteObject(hBmp);
            r = DeleteDC(hDCMemory);
            ReleaseDC(hWndSrc, hDCSrc);

            return ret;
        }

        #endregion

        #region Privates vars

        /// <summary>
        /// struct that holding data that passed to thread callback
        /// </summary>
        struct ComputingOption
        {
            public int DefaultOpacity;
        }

        const int INDICATOR_MODE_HEIGHT = 24;
        const int INDICATOR_MODE_WIDTH = 100;

        bool _setup = false;
        bool _positioned = false;
        bool _fadingIn = false;
        bool _fadingOut = false;
        int _fadeInCounter = 0;
        bool _firstLoad = true;
        bool _indicatorMode = false;
        int _opacity;
        int _defaultOpacity;
        Form _parentForm = null;

        Image _formShot = null;

        private delegate void TSetOpacity(int opacity);
        private TSetOpacity DSetOpacity;
        private delegate void TFadeInInit();
        private TFadeInInit DFadeInInit;
        InvalidateEventHandler _subControlInvalidateHandler = null;

        public event EventHandler FadedIn;
        public event EventHandler FadedOut;

        EventWaitHandle drawingWait = null;
        EventWaitHandle waitingFadedIn = null;
        AnimationOnPaint aop = new AnimationOnPaint(); 


        #endregion

        public Overlay()
        {
            this.SetStyle(ControlStyles.SupportsTransparentBackColor |
                ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint |
                ControlStyles.AllPaintingInWmPaint, true);

            //this.SetStyle(ControlStyles.SupportsTransparentBackColor |
            //    ControlStyles.Opaque , true);

            this.UpdateStyles();

            this.OverlayColor = Color.Gray;
            this.BackColor = Color.Transparent;
            this.Opacity = 50;
            this.IndicatorMode = false;
            this.Visible = false;
            this.Font = new Font(this.Font.FontFamily.Name, 8, FontStyle.Bold);
            this.ForeColor = Color.White;

            drawingWait = new EventWaitHandle(false, EventResetMode.AutoReset);
            waitingFadedIn = new EventWaitHandle(false, EventResetMode.AutoReset);

            DSetOpacity = new TSetOpacity(this.SetOpacity);
            DFadeInInit = new TFadeInInit(this.FadeInInit);

            _subControlInvalidateHandler = new InvalidateEventHandler(HandleSubControlInvalidated);

            //aop.Width = 24;
            //aop.Height = 24;
            aop.Hide();
            this.Controls.Add(aop);

            this.FadedIn += new EventHandler(Overlay_FadedIn);
            this.FadedOut += new EventHandler(Overlay_FadedOut);
            
            InitializeComponent();
        }

        #region Properties

        public Color OverlayColor
        {
            get;
            set;
        }

        /// <summary>
        /// Set the opacity of control
        /// </summary>
        public int Opacity
        {
            set
            {
                int op = value;
                if (op > 100)
                {
                    op = 100;
                }
                else if (op < 0)
                {
                    op = 0;
                }
                this._opacity = op;
                this._defaultOpacity = op;

                //this.Refresh();
                this.Invalidate();
            }
            get
            {
                return this._defaultOpacity;
            }
        }

        /// <summary>
        /// Get or set parentForm, if not set, overlay will try to find one.
        /// </summary>
        public Form ParentForm
        {
            get
            {
                if (this._parentForm != null)
                {
                    return this._parentForm;
                }
                Form ret = null;
                Control ctrl = this;
                while (ctrl != null)
                {
                    if (ctrl is Form)
                    {
                        ret = ctrl as Form;
                        break;
                    }
                    ctrl = ctrl.Parent;
                }

                this._parentForm = ret;
                return ret;
            }
            set
            {
                this._parentForm = value;
            }
        }


        /// <summary>
        /// if true there will be no transparent background block user operation.
        /// </summary>
        public bool IndicatorMode
        {
            get
            {
                return _indicatorMode;
            }
            set
            {
                this.aop.Visible = !value;

                // cause re-positioning.
                _positioned = false;
                _indicatorMode = value;
            }
        }

        #endregion

        #region Public Methods

        public void FadeIn()
        {
            FadeIn(10);
        }

        public void FadeOut()
        {
            FadeOut(10);
        }

        /// <summary>
        /// Async fade in.
        /// </summary>
        /// <param name="opacityStep"></param>
        public void FadeIn(int opacityStep)
        {
            // means already faded in.
            if (_fadeInCounter++ > 0)
            {
                return;
            }

            ComputingOption newOptions = new ComputingOption()
            {
                DefaultOpacity = this.Opacity
            };

            _fadingIn = true;
            _fadingOut = false;

            ThreadPool.QueueUserWorkItem(delegate(object state)
            {
                while (!this.ParentForm.IsHandleCreated || !this.ParentForm.Visible 
                    || this.ParentForm.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                {
                    Thread.Sleep(1000);
                }

                if (_firstLoad)
                {
                    // hard code in order to wait until client painted.
                    Thread.Sleep(300);
                    _firstLoad = false;
                }

                if (!this.ParentForm.IsHandleCreated)
                {
                    return;
                }

                try
                {
                    this.Invoke(DFadeInInit);
                }
                catch (Exception)
                {
                    return;
                }

                Thread c = Thread.CurrentThread;

                ComputingOption options = (ComputingOption)state;
                int op = options.DefaultOpacity;

                int crnt = 0;

                while (true && _fadingIn)
                {
                    if (crnt > op)
                    {
                        crnt = op;
                    }

                    this.Invoke(DSetOpacity, crnt);

                    drawingWait.WaitOne();
                    drawingWait.Reset();

                    if (crnt >= op)
                    {
                        break;
                    }

                    crnt += opacityStep;

                    Thread.Sleep(50);
                }

                if (FadedIn != null)
                {
                    this.Invoke(FadedIn, this, new EventArgs());
                }

                _fadingIn = false;

            }, newOptions);
        }

        /// <summary>
        /// Async fade out.
        /// </summary>
        /// <param name="opacityStep"></param>
        public void FadeOut(int opacityStep)
        {
            // if a fade out process is waiting, then we don't wont the other.
            if (--_fadeInCounter > 0){
                return;
            }

            _fadingIn = false;
            _fadingOut = true;
            ThreadPool.QueueUserWorkItem(delegate(object state)
            {
                while (!this.ParentForm.IsHandleCreated)
                {
                    Thread.Sleep(1000);
                }

                waitingFadedIn.WaitOne();

                Thread c = Thread.CurrentThread;

                int crnt = this._opacity;

                while (true && _fadingOut)
                {
                    if (crnt < 0)
                    {
                        crnt = 0;
                    }

                    if (!this.IsHandleCreated)
                    {
                        return;
                    }

                    try
                    {
                        this.Invoke(DSetOpacity, crnt);
                    }
                    catch (Exception)
                    {
                        return;
                    }

                    drawingWait.WaitOne();
                    drawingWait.Reset();

                    if (crnt <= 0)
                    {
                        break;
                    }

                    crnt -= opacityStep;

                    Thread.Sleep(50);
                }

                if (FadedOut != null)
                {
                    this.Invoke(FadedOut, this, new EventArgs());
                }

                _fadingOut = false;

            });
        } 
        #endregion

        #region Event handlers

        private void Overlay_FadedIn(object sender, EventArgs e)
        {
            if (!this.IndicatorMode)
            {
                aop.Show();
            }
            aop.StartAnimation();
            waitingFadedIn.Set();
            _fadingIn = false;
        }

        private void Overlay_FadedOut(object sender, EventArgs e)
        {
            aop.Hide();
            aop.StopAnimatinon();
            this.Hide();
            //this.Opacity = this._defaultOpacity;
            _fadingOut = false;
        }

        private void ParentForm_Resize(object sender, EventArgs e)
        {
            this._setup = false;
            this.ResizeAOP();
            this.ParentForm.Invalidate();
        }

        private void ParentForm_ControlRemoved(object sender, ControlEventArgs e)
        {
            e.Control.Invalidated -= _subControlInvalidateHandler;
        }

        private void ParentForm_ControlAdded(object sender, ControlEventArgs e)
        {
            if (e.Control == this)
            {
                return;
            }
            e.Control.Invalidated += _subControlInvalidateHandler;
        }

        private void HandleSubControlInvalidated(object sender, InvalidateEventArgs e)
        {
            this.Refresh();
        }

        #endregion

        #region Overrides

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            //base.OnPaintBackground(pevent);
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x20;
                return cp;
            }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            if (this.Visible == false)
            {
                return;
            }

            if (this.ParentForm != null)
            {
                this.Setup();

                Graphics g = pe.Graphics;
                int radius = 5;
                
                Rectangle area = GetWorkingArea();

                area.X = 0;
                area.Y = 0;

                if (IndicatorMode)
                {
                    area.Height -= radius;
                }

                if (this._formShot != null)
                {
                    g.DrawImage(this._formShot, new Point(0, 0));
                }
                

                Brush overlay = new SolidBrush(Color.FromArgb(this._opacity * 255 / 100, this.OverlayColor));

                g.FillRectangle(overlay, area);

                if (this.IndicatorMode)
                {
                    
                    float smoothingFix = 0.5F;

                    g.FillRectangle(overlay, radius - smoothingFix, area.Height , area.Width - radius * 2 + smoothingFix, radius);

                    // draw corner

                    // bottom left
                    g.FillPie(overlay, new Rectangle(0, this.Height - radius * 2, radius * 2, radius * 2), 90, 90);
                    g.FillPie(overlay, new Rectangle(this.Width - radius * 2 , this.Height - radius * 2 , radius * 2, radius * 2), 90, -90);

                    Brush textBrush = new SolidBrush(this.ForeColor);
                    SizeF strblock = g.MeasureString(this.Text, this.Font);
                    g.DrawString(this.Text, this.Font, textBrush, (area.Width - strblock.Width) / 2, (area.Height - strblock.Height) / 2);
                    textBrush.Dispose();
                }

                overlay.Dispose();
            }
            
            base.OnPaint(pe);
            //this.drawingWait.Set();
        }

        #endregion

        #region private methods

        /// <summary>
        /// set opacity and refresh the entire form
        /// this method should only be called in thread
        /// </summary>
        /// <param name="opacity"></param>
        private void SetOpacity(int opacity)
        {
            // do not assign to this.Opacity as it invalidates control
            this._opacity = opacity;
            this.Invalidate();
            this.drawingWait.Set();
        }

        private void FadeInInit()
        {
            this.BringToFront();

            this._opacity = 0;

            if (this._formShot != null)
            {
                this._formShot.Dispose();
            }
            this._formShot = this.CaptureFormScreen();
            
            this.Show();

            this.ResizeAOP();

            this.Invalidate();
        }

        private void Setup()
        {
            if (!_positioned)
            {
                Rectangle area = GetWorkingArea();
                this.Location = new Point(area.X, area.Y);

                if (!this.IndicatorMode && this.IsDesignerHosted())
                {
                    this.Size = new Size(this.ParentForm.ClientSize.Width, 20);
                }
                else if (!this.IndicatorMode && !this.IsDesignerHosted())
                {
                    this.Size = this.ParentForm.ClientSize;
                }
                else if (this.IndicatorMode)
                {
                    this.Size = area.Size;
                }

                _positioned = true;
            }
            if (!this._setup)
            {
                

                // hook controls
                foreach (Control ctrl in this.Parent.Controls)
                {
                    if (ctrl == this)
                    {
                        continue;
                    }
                    ctrl.Invalidated += _subControlInvalidateHandler;
                }

                foreach (Control ctrl in this.ParentForm.Controls)
                {
                    if (ctrl == this)
                    {
                        continue;
                    }
                    ctrl.Invalidated += _subControlInvalidateHandler;
                }

                this.Parent.ControlAdded += new ControlEventHandler(ParentForm_ControlAdded);

                this.Parent.ControlRemoved += new ControlEventHandler(ParentForm_ControlRemoved);

                this.ParentForm.ControlAdded += new ControlEventHandler(ParentForm_ControlAdded);

                this.ParentForm.ControlRemoved += new ControlEventHandler(ParentForm_ControlRemoved);

                this.ParentForm.Resize += new EventHandler(ParentForm_Resize);

                this._setup = true;
            }
        }

        private void ResizeAOP()
        {
            if (aop != null)
            {
                aop.Location = new Point((this.ParentForm.ClientSize.Width - aop.Width) / 2,
                    (this.ParentForm.ClientSize.Height - aop.Height) / 2);
            }
        }

        private Image CaptureFormScreen()
        {
            if (this.ParentForm == null)
            {
                return null;
            }

            this.ParentForm.Activate();
            //this.ParentForm.SuspendLayout();

            //_waitingScreenShot = true;
            //this.ParentForm.Invalidate();

            //while (true)
            //{
            //    if (_waitingScreenShot)
            //    {
            //        Application.DoEvents();
            //    }
            //    else
            //    {
            //        break;
            //    }
            //}

            // Set the bitmap object to the size of the screen
            Bitmap bmpScreenshot = new Bitmap(this.ParentForm.ClientSize.Width, this.ParentForm.ClientSize.Height);

            //// Create a graphics object from the bitmap
            //Graphics gfxScreenshot = Graphics.FromImage(bmpScreenshot);

            //// point to screen
            //Point pointToScreen = this.ParentForm.PointToScreen(new Point(0, 0));

            //// Take the screenshot from the upper left corner to the right bottom corner
            //gfxScreenshot.CopyFromScreen(pointToScreen.X,
            //    pointToScreen.Y, 0, 0, this.ParentForm.ClientSize, CopyPixelOperation.SourceCopy);
            //// Save the screenshot to the specified path that the user has chosen

            //gfxScreenshot.Dispose();

            Rectangle area = this.GetWorkingArea();

            bmpScreenshot = CaptureWindow(this.ParentForm.Handle, true, area.X, area.Y, area.Width, area.Height);

            //this.ParentForm.ResumeLayout();
            return bmpScreenshot;
        }

        private Rectangle GetWorkingArea()
        {
            Rectangle ret = new Rectangle();

            if (IndicatorMode)
            {
                ret.Height = INDICATOR_MODE_HEIGHT;
                ret.Width = INDICATOR_MODE_WIDTH;
                ret.X = (this.ParentForm.Width - ret.Width) / 2;
                ret.Y = 0;
            }
            else
            {
                ret.Height = this.ParentForm.ClientSize.Height;
                ret.Width = this.ParentForm.ClientSize.Width;
                ret.X = 0;
                ret.Y = 0;
            }

            return ret;
        }

        #endregion
    }
}
